package org.googlecode.viewt.core;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.apache.velocity.runtime.log.Log;
import org.googlecode.viewt.core.exception.ResourceFailException;
import org.googlecode.viewt.core.exception.ResourceNotFoundException;
import org.googlecode.viewt.core.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

public abstract class ReConfigurableFileCfgRepository implements
		IReConfigurableResources {
	public static final String PROP_CHECK_INTERVAL = "CHECK_INTERVAL";
	public static final int DEFAULT_FILE_CHECK = 30;
	public static final String PROP_BASEPATH = "BASEPATH";
	public static final String PROP_FILE_PATTERN = "FILE_PATTERN";
	public static final String PROP_INIT_LOAD = "INIT_LOAD";
	public static final boolean DEFAULT_INIT_LOAD = true;
	protected final Logger logger;
	private Properties props;
	private Map resMap;
	private Map<String, Date> timeMap;
	private Map loadMap;

	public ReConfigurableFileCfgRepository() {
		this.logger = LoggerFactory.getLogger((super.getClass()));

		this.props = null;

		this.resMap = Collections.synchronizedMap(new HashMap());

		this.timeMap = Collections.synchronizedMap(new HashMap<String, Date>());

		this.loadMap = Collections.synchronizedMap(new HashMap());
	}

	public Properties getInitProperties() {
		return this.props;
	}

	public void setInitProperties(Properties initProperties) {
		this.props = initProperties;
	}

	public int getCheckInterval() {
		if (this.props == null) {
			if (this.logger.isDebugEnabled())
				this.logger
						.debug("file check interval not set, use default:30");
			return 30;
		}

		String propInterval = this.props.getProperty("CHECK_INTERVAL");
		if (propInterval != null) {
			try {
				return Integer.parseInt(propInterval);
			} catch (NumberFormatException ex) {
				this.logger
						.warn("Invalid file check interval, check initial property setting:"
								+ propInterval);
				this.logger.info("Use default check interval:30");
				return 30;
			}
		}

		if (this.logger.isDebugEnabled())
			this.logger.debug("file check interval not set, use default:30");
		return 30;
	}

	public String getBasePath() {
		String basePath = (this.props == null) ? null : this.props
				.getProperty("BASEPATH");
		if (basePath == null) {
			this.logger.error("file base path not set, check your setting");
		}
		return StringUtil.replaceEnv(basePath);
	}

	public String getFilePattern() {
		String filePattern = (this.props == null) ? null : this.props
				.getProperty("FILE_PATTERN");
		if (filePattern == null) {
			this.logger.error("file pattern not set, check your setting");
		}
		return StringUtil.replaceEnv(filePattern);
	}

	public boolean isInitLoad() {
		String loadOption = (this.props == null) ? null : this.props
				.getProperty("INIT_LOAD");
		if (loadOption != null) {
			return Boolean.getBoolean(loadOption);
		}
		return true;
	}

	public Object getResource(String resourceUrl)
			throws ResourceNotFoundException, ResourceFailException {
		
		if (resourceUrl == null) {
			return null;
		}
		Object resourceObj = null;
		Date loadTime = null;
		Date checkTime = null;

		checkTime = (Date) this.timeMap.get(resourceUrl);
		if (checkTime == null) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Resource:" + resourceUrl
						+ " not found in repository, try to load it");
			}
			resourceObj = reloadResource(resourceUrl);
			addResource(resourceUrl, resourceObj);
			this.timeMap.put(resourceUrl, new Date());

			if (this.logger.isDebugEnabled())
				this.logger.debug("Resource:" + resourceUrl
						+ " load successful");
			return resourceObj;
		}

		resourceObj = this.resMap.get(resourceUrl);
		if (needCheckReload(resourceUrl, resourceObj, checkTime)) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Resource:" + resourceUrl + " last checked:"
						+ checkTime + ", file need check again");
			}
			this.timeMap.put(resourceUrl, new Date());
			loadTime = (Date) this.loadMap.get(resourceUrl);

			if (needReload(resourceUrl, resourceObj, loadTime)) {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Resource:" + resourceUrl
							+ " modified since last load:" + loadTime
							+ ", reload resource again");
				}
				resourceObj = reloadResource(resourceUrl);
				addResource(resourceUrl, resourceObj);

				if (this.logger.isDebugEnabled())
					this.logger.debug("Resource:" + resourceUrl
							+ " reload successful");
				return resourceObj;
			}
		}

		return resourceObj;
	}

	public Object addResource(String resourceUrl, Object resourceObj) {
		if (resourceUrl == null) {
			return null;
		}
		resourceUrl = StringUtil.replaceSeparator(resourceUrl);
		this.loadMap.put(resourceUrl, new Date());
		return this.resMap.put(resourceUrl, resourceObj);
	}

	public Object removeResource(String resourceUrl) {
		if (resourceUrl == null) {
			return null;
		}
		resourceUrl = StringUtil.replaceSeparator(resourceUrl);
		this.loadMap.remove(resourceUrl);
		this.timeMap.remove(resourceUrl);
		return this.resMap.remove(resourceUrl);
	}

	public String[] getResourceUrls() {
		Iterator iterator = this.resMap.keySet().iterator();

		String[] urls = new String[this.resMap.size()];
		int i = 0;
		while ((iterator != null) && (iterator.hasNext())) {
			urls[(i++)] = ((String) iterator.next());
		}
		return urls;
	}

	public void loadAll() throws ResourceFailException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Load all resource file");
		}
		String basePath = getBasePath();
		String filePattern = getFilePattern();
		if ((basePath == null) || (filePattern == null)) {
			this.logger.error("basePath=" + basePath + ", filePattern="
					+ filePattern + ", could not load resource");
			return;
		}

		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Load all resource file, basePath:" + basePath
					+ ", filePattern:" + filePattern);
		}
		String[] resourceFiles = getMatchingFiles(basePath, filePattern);
		if (this.logger.isDebugEnabled()) {
			for (int i = 0; i < resourceFiles.length; ++i) {
				this.logger.debug("Resource file:" + resourceFiles[i]
						+ " found");
			}
		}

		try {
			touchResources(resourceFiles);
		} catch (ResourceNotFoundException ex) {
			this.logger.error("File resource not found", ex);
		}
	}

	public void init() throws ResourceFailException {
		if (isInitLoad())
			loadAll();
	}

	public boolean needCheckReload(String resourceUrl, Object resourceObj,
			Date lastCheck) {
		long past = (System.currentTimeMillis() - lastCheck.getTime()) / 1000L;

		return (past <= getCheckInterval());
	}

	public boolean needReload(String resourceUrl, Object resourceObj,
			Date lastLoad) {
		File resourceFile = new File(resourceUrl);

		if ((!(resourceFile.exists())) || (resourceFile.isDirectory())) {
			return false;
		}

		long lastModify = resourceFile.lastModified();
		if (lastModify > lastLoad.getTime()) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Resource:" + resourceUrl
						+ " modified, need reload it");
			}
			return true;
		}

		return false;
	}

	public static String[] getMatchingFiles(String basePath, String filePattern) {
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

		String pattern = null;
		if (basePath.endsWith(File.separator))
			pattern = "file:" + basePath + filePattern;
		else {
			pattern = "file:" + basePath + File.separator + filePattern;
		}
		Logger logger = LoggerFactory.getLogger(ReConfigurableFileCfgRepository.class);
		try {
			Resource[] res = resolver.getResources(pattern);
			int size = (res == null) ? 0 : res.length;
			String[] files = new String[size];
			for (int i = 0; i < size; ++i) {
				files[i] = res[i].getFile().getAbsolutePath();
			}
			return files;
		} catch (IOException ioEx) {
			logger.error("Get matching file list fail", ioEx);
		}

		return new String[0];
	}

	public void touchResources(String[] resourceFiles)
			throws ResourceNotFoundException, ResourceFailException {
		int size = (resourceFiles == null) ? 0 : resourceFiles.length;
		for (int i = 0; i < size; ++i)
			getResource(resourceFiles[i]);
	}
}